#include "common.h"
#include "counter.h"
#ifdef __cplusplus
extern "C"
{
#endif

    typedef enum
    {
        FU_ALU = 0b1,
        FU_BRU = 0b10,
        FU_LSU = 0b100,
        FU_MDU = 0b1000,
        FU_MISC = 0b10000
    } FU_Type;

    typedef enum
    {
        // RV32I
        ALU_ADD,
        ALU_SUB,
        ALU_AND,
        ALU_OR,
        ALU_XOR,
        ALU_SLL,
        ALU_SRA,
        ALU_SRL,
        ALU_SLT,
        ALU_SLTU
    } ALU_OP;

    typedef enum
    {
        // RV32I
        LSU_LB,
        LSU_LBU,
        LSU_LH,
        LSU_LHU,
        LSU_LW,
        LSU_SB,
        LSU_SH,
        LSU_SW,
        // RV64I
        LSU_LD,
        LSU_LWU,
        LSU_SD
    } LSU_OP;

    typedef enum
    { // RV32I
        BRU_BEQ,
        BRU_BGE,
        BRU_BGEU,
        BRU_BLT,
        BRU_BLTU,
        BRU_BNE,
        BRU_JAL,
        BRU_JALR
    } BRU_OP;

    typedef enum
    {
        // RV32M
        MDU_DIV,
        MDU_DIVU,
        MDU_REMU,
        MDU_REM,
        MDU_MUL,
        MDU_MULH,
        MDU_MULHSU,
        MDU_MULHU
    } MDU_OP;

    typedef enum
    {
        MISC_CSRRW,
        MISC_CSRRS,
        MISC_CSRRC,
        MISC_CSRRWI,
        MISC_CSRRSI,
        MISC_CSRRCI,
        MISC_MRET,
        MISC_EBREAK,
        MISC_ECALL
    } MISC_OP;

    typedef struct
    {
        uint32_t imm32;
        uint8_t rs1, rs2, rd;
        uint8_t fu_op;
        FU_Type fu_type;
        uint8_t flush_pipeline;
        uint8_t is_op32;
        uint8_t has_imm;
        uint8_t illegal_inst;
    } DecodeOP;
    // --- pattern matching mechanism ---
    __attribute__((always_inline)) static inline void pattern_decode(const char *str, int len,
                                                                     uint64_t *key, uint64_t *mask, uint64_t *shift)
    {
        uint64_t __key = 0, __mask = 0, __shift = 0;
#define macro(i)                                         \
    if ((i) >= len)                                      \
        goto finish;                                     \
    else                                                 \
    {                                                    \
        char c = str[i];                                 \
        if (c != ' ')                                    \
        {                                                \
            __key = (__key << 1) | (c == '1' ? 1 : 0);   \
            __mask = (__mask << 1) | (c == '?' ? 0 : 1); \
            __shift = (c == '?' ? __shift + 1 : 0);      \
        }                                                \
    }

#define macro2(i) \
    macro(i);     \
    macro((i) + 1)
#define macro4(i) \
    macro2(i);    \
    macro2((i) + 2)
#define macro8(i) \
    macro4(i);    \
    macro4((i) + 4)
#define macro16(i) \
    macro8(i);     \
    macro8((i) + 8)
#define macro32(i) \
    macro16(i);    \
    macro16((i) + 16)
#define macro64(i) \
    macro32(i);    \
    macro32((i) + 32)
        macro64(0);
#undef macro
    finish:
        *key = __key >> __shift;
        *mask = __mask >> __shift;
        *shift = __shift;
    }

    enum
    {
        TYPE_I,
        TYPE_U,
        TYPE_S,
        TYPE_R,
        TYPE_J,
        TYPE_B,
        TYPE_N, // none
    };
#define BITMASK(bits) ((1ull << (bits)) - 1)
#define BITS(x, hi, lo) (((x) >> (lo)) & BITMASK((hi) - (lo) + 1)) // similar to x[hi:lo] in verilog
#define SEXT(x, len) ({ struct { int64_t n : len; } __x = { .n = x }; (uint64_t)__x.n; })
// macro stringizing
#define str_temp(x) #x
#define str(x) str_temp(x)

#define src1R()        \
    do                 \
    {                  \
        dop.rs1 = rs1; \
    } while (0)
#define src2R()        \
    do                 \
    {                  \
        dop.rs2 = rs2; \
    } while (0)
#define immI()                                 \
    do                                         \
    {                                          \
        dop.has_imm = 1;                       \
        dop.imm32 = SEXT(BITS(i, 31, 20), 12); \
    } while (0)
#define immU()                                       \
    do                                               \
    {                                                \
        dop.has_imm = 1;                             \
        dop.imm32 = SEXT(BITS(i, 31, 12), 20) << 12; \
    } while (0)
#define immS()                                                        \
    do                                                                \
    {                                                                 \
        dop.has_imm = 1;                                              \
        dop.imm32 = (SEXT(BITS(i, 31, 25), 7) << 5) | BITS(i, 11, 7); \
    } while (0)
#define immJ()                                                                                                                        \
    do                                                                                                                                \
    {                                                                                                                                 \
        dop.has_imm = 1;                                                                                                              \
        dop.imm32 = SEXT(((BITS(i, 31, 31) << 20) | (BITS(i, 30, 21) << 1) | (BITS(i, 20, 20) << 11) | (BITS(i, 19, 12) << 12)), 21); \
    } while (0)
#define immB()                                                                                                                    \
    do                                                                                                                            \
    {                                                                                                                             \
        dop.has_imm = 1;                                                                                                          \
        dop.imm32 = SEXT(((BITS(i, 31, 31) << 12) | (BITS(i, 30, 25) << 5) | (BITS(i, 11, 8) << 1) | (BITS(i, 7, 7) << 11)), 13); \
    } while (0)
    char debug_buf[128] = "";
    static void decode_operand(uint32_t i, DecodeOP &dop, int type)
    {
        int rd = BITS(i, 11, 7);
        int rs1 = BITS(i, 19, 15);
        int rs2 = BITS(i, 24, 20);
        switch (type)
        {
        case TYPE_I:
            src1R();
            immI();
            break;
        case TYPE_U:
            immU();
            break;
        case TYPE_S:
            src1R();
            src2R();
            immS();
            rd = 0;
            break;
        case TYPE_R:
            src1R();
            src2R();
            break;
        case TYPE_J:
            immJ();
            break;
        case TYPE_B:
            src1R();
            src2R();
            immB();
            rd = 0;
            break;
        case TYPE_N:
            break;
        }
        dop.rd = rd;
    }

// --- pattern matching wrappers for decode ---
#define INSTPAT(pattern, ...)                                          \
    do                                                                 \
    {                                                                  \
        uint64_t key, mask, shift;                                     \
        pattern_decode(pattern, STRLEN(pattern), &key, &mask, &shift); \
        if ((((uint64_t)INSTPAT_INST(s) >> shift) & mask) == key)      \
        {                                                              \
            INSTPAT_MATCH(s, ##__VA_ARGS__);                           \
            goto __instpat_end_;                                       \
        }                                                              \
    } while (0)

#define concat_temp(x, y) x##y
#define concat(x, y) concat_temp(x, y)
// strlen() for string constant
#define STRLEN(CONST_STR) (sizeof(CONST_STR) - 1)

#define INSTPAT_START(name) \
    {                       \
        const void **__instpat_end = &&concat(__instpat_end_, name);
#define INSTPAT_END(name)           \
    concat(__instpat_end_, name) :; \
    }

#define INSTPAT_INST(s) (word_t)(s)
#define INSTPAT_MATCH(s, name, type, ... /* execute body */) \
    {                                                        \
        sprintf(debug_buf,"inst 0x%x match %s", s, str(name));             \
        decode_operand(s, dop, concat(TYPE_, type));         \
        __VA_ARGS__;                                         \
    }

    DecodeOP decode(uint32_t s)
    {
        DecodeOP dop = {0};
        // INSTPAT_START();
        {
            // RV64I
            INSTPAT("??????? ????? ????? ??? ????? 00101 11", auipc, U, dop.fu_type = FU_ALU; dop.fu_op = ALU_ADD;);
            INSTPAT("??????? ????? ????? ??? ????? 01101 11", lui, U, dop.fu_type = FU_ALU; dop.fu_op = ALU_ADD;);
            INSTPAT("??????? ????? ????? 000 ????? 00110 11", addiw, I, dop.is_op32 = 1; dop.fu_type = FU_ALU; dop.fu_op = ALU_ADD;);
            INSTPAT("0000000 ????? ????? 000 ????? 01110 11", addw, R, dop.is_op32 = 1; dop.fu_type = FU_ALU; dop.fu_op = ALU_ADD;);
            INSTPAT("??????? ????? ????? 011 ????? 00000 11", ld, I, dop.fu_type = FU_LSU; dop.fu_op = LSU_LD;);
            INSTPAT("??????? ????? ????? 110 ????? 00000 11", lwu, I, dop.fu_type = FU_LSU; dop.fu_op = LSU_LWU;);
            INSTPAT("??????? ????? ????? 011 ????? 01000 11", sd, S, dop.fu_type = FU_LSU; dop.fu_op = LSU_SD;);
            INSTPAT("000000? ????? ????? 001 ????? 00100 11", slli, I, dop.fu_type = FU_ALU; dop.fu_op = ALU_SLL;);
            INSTPAT("0000000 ????? ????? 001 ????? 00110 11", slliw, I, dop.is_op32 = 1; dop.fu_type = FU_ALU; dop.fu_op = ALU_SLL;);
            INSTPAT("0000000 ????? ????? 001 ????? 01110 11", sllw, R, dop.is_op32 = 1; dop.fu_type = FU_ALU; dop.fu_op = ALU_SLL;);
            INSTPAT("010000? ????? ????? 101 ????? 00100 11", srai, I, dop.fu_type = FU_ALU; dop.fu_op = ALU_SRA;);
            INSTPAT("0100000 ????? ????? 101 ????? 00110 11", sraiw, I, dop.is_op32 = 1; dop.fu_type = FU_ALU; dop.fu_op = ALU_SRA;);
            INSTPAT("0100000 ????? ????? 101 ????? 01110 11", sraw, R, dop.is_op32 = 1; dop.fu_type = FU_ALU; dop.fu_op = ALU_SRA;);
            INSTPAT("000000? ????? ????? 101 ????? 00100 11", srli, I, dop.fu_type = FU_ALU; dop.fu_op = ALU_SRL;);
            INSTPAT("0000000 ????? ????? 101 ????? 00110 11", srliw, I, dop.is_op32 = 1; dop.fu_type = FU_ALU; dop.fu_op = ALU_SRL;);
            INSTPAT("0000000 ????? ????? 101 ????? 01110 11", srlw, R, dop.is_op32 = 1; dop.fu_type = FU_ALU; dop.fu_op = ALU_SRL;);
            INSTPAT("0100000 ????? ????? 000 ????? 01110 11", subw, R, dop.is_op32 = 1; dop.fu_type = FU_ALU; dop.fu_op = ALU_SUB;);
            // RV64M
            INSTPAT("0000001 ????? ????? 101 ????? 01110 11", DIVUW, R, dop.is_op32 = 1; dop.fu_type = FU_MDU; dop.fu_op = MDU_DIVU;);
            INSTPAT("0000001 ????? ????? 100 ????? 01110 11", DIVW, R, dop.is_op32 = 1; dop.fu_type = FU_MDU; dop.fu_op = MDU_DIV;);
            INSTPAT("0000001 ????? ????? 111 ????? 01110 11", REMUW, R, dop.is_op32 = 1; dop.fu_type = FU_MDU; dop.fu_op = MDU_REMU;);
            INSTPAT("0000001 ????? ????? 110 ????? 01110 11", REMW, R, dop.is_op32 = 1; dop.fu_type = FU_MDU; dop.fu_op = MDU_REM;);
            INSTPAT("0000001 ????? ????? 000 ????? 01110 11", MULW, R, dop.is_op32 = 1; dop.fu_type = FU_MDU; dop.fu_op = MDU_MUL;);
            // RV32M
            INSTPAT("0000001 ????? ????? 100 ????? 01100 11", DIV, R, dop.fu_type = FU_MDU; dop.fu_op = MDU_DIV;);
            INSTPAT("0000001 ????? ????? 101 ????? 01100 11", DIVU, R, dop.fu_type = FU_MDU; dop.fu_op = MDU_DIVU;);
            INSTPAT("0000001 ????? ????? 111 ????? 01100 11", REMU, R, dop.fu_type = FU_MDU; dop.fu_op = MDU_REMU;);
            INSTPAT("0000001 ????? ????? 110 ????? 01100 11", REM, R, dop.fu_type = FU_MDU; dop.fu_op = MDU_REM;);
            INSTPAT("0000001 ????? ????? 000 ????? 01100 11", MUL, R, dop.fu_type = FU_MDU; dop.fu_op = MDU_MUL;);
            INSTPAT("0000001 ????? ????? 001 ????? 01100 11", MULH, R, dop.fu_type = FU_MDU; dop.fu_op = MDU_MULH;);
            INSTPAT("0000001 ????? ????? 010 ????? 01100 11", MULHSU, R, dop.fu_type = FU_MDU; dop.fu_op = MDU_MULHSU;);
            INSTPAT("0000001 ????? ????? 011 ????? 01100 11", MULHU, R, dop.fu_type = FU_MDU; dop.fu_op = MDU_MULHU;);
            // RV32I
            INSTPAT("0000000 ????? ????? 000 ????? 01100 11", add, R, dop.fu_type = FU_ALU; dop.fu_op = ALU_ADD;);
            INSTPAT("??????? ????? ????? 000 ????? 00100 11", addi, I, dop.fu_type = FU_ALU; dop.fu_op = ALU_ADD;);
            INSTPAT("0000000 ????? ????? 111 ????? 01100 11", and, R, dop.fu_type = FU_ALU; dop.fu_op = ALU_AND;);
            INSTPAT("??????? ????? ????? 111 ????? 00100 11", andi, I, dop.fu_type = FU_ALU; dop.fu_op = ALU_AND;);

            INSTPAT("??????? ????? ????? 000 ????? 11000 11", beq, B, dop.fu_type = FU_BRU; dop.fu_op = BRU_BEQ;);
            INSTPAT("??????? ????? ????? 101 ????? 11000 11", bge, B, dop.fu_type = FU_BRU; dop.fu_op = BRU_BGE;);
            INSTPAT("??????? ????? ????? 111 ????? 11000 11", bgeu, B, dop.fu_type = FU_BRU; dop.fu_op = BRU_BGEU;);
            INSTPAT("??????? ????? ????? 100 ????? 11000 11", blt, B, dop.fu_type = FU_BRU; dop.fu_op = BRU_BLT;);
            INSTPAT("??????? ????? ????? 110 ????? 11000 11", bltu, B, dop.fu_type = FU_BRU; dop.fu_op = BRU_BLTU;);
            INSTPAT("??????? ????? ????? 001 ????? 11000 11", bne, B, dop.fu_type = FU_BRU; dop.fu_op = BRU_BNE;);
            INSTPAT("??????? ????? ????? ??? ????? 11011 11", jal, J, dop.fu_type = FU_BRU; dop.fu_op = BRU_JAL;);
            INSTPAT("??????? ????? ????? 000 ????? 11001 11", jalr, I, dop.fu_type = FU_BRU; dop.fu_op = BRU_JALR;);

            INSTPAT("0000000 00000 00000 000 00000 11100 11", ecall, N, dop.flush_pipeline = 1; dop.fu_type = FU_MISC; dop.fu_op = MISC_ECALL;);
            // INSTPAT("??????? ????? ????? 000 ????? 00011 11", fence, N, dop.fu_type = FU_MISC; dop.fu_op = MISC_F );

            INSTPAT("??????? ????? ????? 000 ????? 00000 11", lb, I, dop.fu_type = FU_LSU; dop.fu_op = LSU_LB;);
            INSTPAT("??????? ????? ????? 100 ????? 00000 11", lbu, I, dop.fu_type = FU_LSU; dop.fu_op = LSU_LBU;);
            INSTPAT("??????? ????? ????? 001 ????? 00000 11", lh, I, dop.fu_type = FU_LSU; dop.fu_op = LSU_LH;);
            INSTPAT("??????? ????? ????? 101 ????? 00000 11", lhu, I, dop.fu_type = FU_LSU; dop.fu_op = LSU_LHU;);
            INSTPAT("??????? ????? ????? 010 ????? 00000 11", lw, I, dop.fu_type = FU_LSU; dop.fu_op = LSU_LW;);

            INSTPAT("0000000 ????? ????? 110 ????? 01100 11", or, R, dop.fu_type = FU_ALU; dop.fu_op = ALU_OR;);
            INSTPAT("??????? ????? ????? 110 ????? 00100 11", ori, I, dop.fu_type = FU_ALU; dop.fu_op = ALU_OR;);
            INSTPAT("??????? ????? ????? 000 ????? 01000 11", sb, S, dop.fu_type = FU_LSU; dop.fu_op = LSU_SB;);
            INSTPAT("??????? ????? ????? 001 ????? 01000 11", sh, S, dop.fu_type = FU_LSU; dop.fu_op = LSU_SH;);
            INSTPAT("??????? ????? ????? 010 ????? 01000 11", sw, S, dop.fu_type = FU_LSU; dop.fu_op = LSU_SW;);
            INSTPAT("0000000 ????? ????? 001 ????? 01100 11", sll, R, dop.fu_type = FU_ALU; dop.fu_op = ALU_SLL;);
            INSTPAT("0000000 ????? ????? 010 ????? 01100 11", slt, R, dop.fu_type = FU_ALU; dop.fu_op = ALU_SLT;);
            INSTPAT("??????? ????? ????? 010 ????? 00100 11", slti, I, dop.fu_type = FU_ALU; dop.fu_op = ALU_SLT;);
            INSTPAT("??????? ????? ????? 011 ????? 00100 11", sltiu, I, dop.fu_type = FU_ALU; dop.fu_op = ALU_SLTU;);
            INSTPAT("0000000 ????? ????? 011 ????? 01100 11", sltu, R, dop.fu_type = FU_ALU; dop.fu_op = ALU_SLTU;);
            INSTPAT("0100000 ????? ????? 101 ????? 01100 11", sra, R, dop.fu_type = FU_ALU; dop.fu_op = ALU_SRA;);
            INSTPAT("0000000 ????? ????? 101 ????? 01100 11", srl, R, dop.fu_type = FU_ALU; dop.fu_op = ALU_SRL;);
            INSTPAT("0100000 ????? ????? 000 ????? 01100 11", sub, R, dop.fu_type = FU_ALU; dop.fu_op = ALU_SUB;);
            INSTPAT("0000000 ????? ????? 100 ????? 01100 11", xor, R, dop.fu_type = FU_ALU; dop.fu_op = ALU_XOR;);
            INSTPAT("??????? ????? ????? 100 ????? 00100 11", xori, I, dop.fu_type = FU_ALU; dop.fu_op = ALU_XOR;);

            INSTPAT("0000000 00001 00000 000 00000 11100 11", ebreak, N, dop.flush_pipeline = 1; dop.fu_type = FU_MISC; dop.fu_op = MISC_EBREAK;);
            // Zicsr
            INSTPAT("??????? ????? ????? 001 ????? 11100 11", csrrw, I, dop.flush_pipeline = 1; dop.fu_type = FU_MISC; dop.fu_op = MISC_CSRRW;);
            INSTPAT("??????? ????? ????? 010 ????? 11100 11", csrrs, I, dop.flush_pipeline = 1; dop.fu_type = FU_MISC; dop.fu_op = MISC_CSRRS;);
            INSTPAT("??????? ????? ????? 011 ????? 11100 11", csrrc, I, dop.flush_pipeline = 1; dop.fu_type = FU_MISC; dop.fu_op = MISC_CSRRC;);
            INSTPAT("??????? ????? ????? 101 ????? 11100 11", csrrwi, I, dop.flush_pipeline = 1; dop.fu_type = FU_MISC; dop.fu_op = MISC_CSRRWI;);
            INSTPAT("??????? ????? ????? 110 ????? 11100 11", csrrsi, I, dop.flush_pipeline = 1; dop.fu_type = FU_MISC; dop.fu_op = MISC_CSRRSI;);
            INSTPAT("??????? ????? ????? 111 ????? 11100 11", csrrci, I, dop.flush_pipeline = 1; dop.fu_type = FU_MISC; dop.fu_op = MISC_CSRRCI;);
            // Trap-Return Instructions
            INSTPAT("0011000 00010 00000 000 00000 11100 11", mret, N, dop.flush_pipeline = 1;dop.fu_type = FU_MISC; dop.fu_op = MISC_MRET;);

            INSTPAT("??????? ????? ????? ??? ????? ????? ??", inv, N, dop.illegal_inst = 1;);
        // INSTPAT_END();
        __instpat_end_:;
        }
        return dop;
    }

#ifdef __cplusplus
}
#endif

#include "Vrv_decoder_test.h"
#define MAX_WAVE 10000
#define TRACE 1
VerilatedFstC *tfp;
Vrv_decoder_test top;
static uint64_t dump_time = 0;
void dump()
{
#if TRACE
    if (dump_time < MAX_WAVE)
    {
        tfp->dump(dump_time++);
    }
#endif
}
void eval()
{
    top.eval();
    // dump();
}

int main()
{
    srand(0);
#if TRACE
    Verilated::traceEverOn(true);
    tfp = new VerilatedFstC();
    top.trace(tfp, 0);
    tfp->open("Vrv_decoder_test.fst");
#endif
#define TEST_BEGIN (0)
#define TEST_CNT ((1ull << 32) - TEST_BEGIN)
    uint32_t inst = TEST_BEGIN;
    uint32_t tmp = 0;
    uint64_t run_time = get_time_us();
    size_t i;
    for (i = 0; i < TEST_CNT; i++)
    {
        top.inst = inst;
        eval();
        if(i%10240000 == 0) {
            printf("\rrunning  %.03f%% at %u",100.0f*i/TEST_CNT,inst);
            fflush(stdout);
        }
        auto ref = decode(inst);
        if (ref.illegal_inst)
        {
            if (!top.dop_illegal_inst)
            {
                LOG("inst 0x%x decode illegal_inst 0x%x error at %lu", inst, ref.illegal_inst, dump_time);
                break;
            }
        }
        else
        {
            if (ref.imm32 != top.dop_imm32)
            {
                LOG("inst 0x%x decode imm32 0x%x error at %lu", inst, ref.imm32, dump_time);
                break;
            }
            if (ref.rs1 != top.dop_rs1)
            {
                LOG("inst 0x%x decode rs1 0x%x error at %lu", inst, ref.rs1, dump_time);
                break;
            }
            if (ref.rs2 != top.dop_rs2)
            {
                LOG("inst 0x%x decode rs2 0x%x error at %lu", inst, ref.rs2, dump_time);
                break;
            }
            if (ref.rd != top.dop_rd)
            {
                LOG("inst 0x%x decode rd 0x%x error at %lu", inst, ref.rd, dump_time);
                break;
            }
            if (ref.fu_op != top.dop_fu_op)
            {
                LOG("inst 0x%x decode fu_op 0x%x error at %lu", inst, ref.fu_op, dump_time);
                break;
            }
            if (ref.fu_type != top.dop_fu_type)
            {
                LOG("inst 0x%x decode fu_type 0x%x error at %lu", inst, ref.fu_type, dump_time);
                break;
            }
            if (ref.flush_pipeline != top.dop_flush_pipeline)
            {
                LOG("inst 0x%x decode flush_pipeline 0x%x error at %lu", inst, ref.flush_pipeline, dump_time);
                break;
            }
            if (ref.is_op32 != top.dop_is_op32)
            {
                LOG("inst 0x%x decode is_op32 0x%x error at %lu", inst, ref.is_op32, dump_time);
                break;
            }
            if (ref.has_imm != top.dop_has_imm)
            {
                LOG("inst 0x%x decode has_imm 0x%x error at %lu", inst, ref.has_imm, dump_time);
                break;
            }
            if (ref.illegal_inst != top.dop_illegal_inst)
            {
                LOG("inst 0x%x decode illegal_inst 0x%x error at %lu", inst, ref.illegal_inst, dump_time);
                break;
            }
        }
        inst++;
    }
    LOG("%s",debug_buf);
    run_time = get_time_us() - run_time;
    if (run_time > 0)
    {
        LOG("simulation speed: %.02f MHz\n", (float)i / run_time);
    }
#if TRACE
    dump();
    tfp->close();
    delete tfp;
#endif
    return 0;
}